#include <con.h>
#include <vga.h>
#include <string.h>
#include <sem.h>
#include <mm.h>
#include <i386.h>

#ifndef CON_DEFAULT_COLOR
#define CON_DEFAULT_COLOR VGA_FC_WHITE
#endif /*CON_DEFAULT_COLOR*/

static char *ascii_art[] = {
"$$$$$$$$$$$$$$$$\"\"$o$o$o$o$o$oo$$\"\"$$$$$$$$$$$$$$$",
"$$$$$$$$$$$$\"\"o$$$$$$$$$$\"$\"$$$$$$$o$\"$$$$$$$$$$$$",
"$$$$$$$$$\"$o$$$$\"\"$oo $ \"\"      \"\"\"$$$oo\"$$$$$$$$$",
"$$$$$$$\"o$$$$\"   \"\"o  $oo o o       \"\"$$$o\"$$$$$$$",
"$$$$$\"o$$$\"       oo$$$$$$$$$$o        \"$$$o\"$$$$$",
"$$$$\"o$$$  $  o$$$$$$$$$$$$$$\"$$oo       \"$$$ $$$$",
"$$$\"$$$\"   \"$$$$$$$$$$$$$$$$o$o$$$\"        $$$o$$$",
"$$ $$$    o$$$$$$$$$$$$$$$$$$$$$$$$o o   o  \"$$o\"$",
"$\"$$$\"    o$$$$$$$$$\"$$$$$$\"\" \"$$$$$$\"$$$$$  $$$\"$",
"$o$$\"    o$$$$$$$$$$o\"\"$$$\"\"\"\"ooo\"$$$$$$$$\"   $$$\"",
"$o$$\"    o$$$$$$$$$$            \"\"oo\"$\"$o\"\"   $$$o",
"o$$$     o$$$$$$$$$$                \"\"\"\"\"$    o$$o",
"o$$$    o$$$$$$$$$$$$o                   \"o \"oo$$o",
"o$$$  oo$$$$$$$$$$$$$$$$ooooooo$$$$$oo    $\"$ \"$$o",
"o$$$\"  \"\"  $$$$$$$$$$$$$$$$$$$$$$$$$$$$o    \" $$$",
"$ $$$       \"$$$$$$$$$$$$$$$$$$$$$$$$$$$o    o$$\"$",
"$$\"$$o       \"$$$$$$$$$$$$$$$$$$$$$$$$$$$o   $$$o$",
"$$o$$$o       $$\"\"$$$$$$$$$$$$$$$$$$$$$$$o  $$$ $$",
"$$$o\"$$o    \"$\"\"  \"$\"\"$$$$$$$$$$$$$$$$$$$oo$$$\"$$$",
"$$$$o\"$$$o        \"     $$$$$$$$$$$$$$$$$o$$\"o$$$$",
"$$$$$$o\"$$$o         oo$$$$$$$$$$$$$$$$$$$$\"o$$$$$",
"$$$$$$$$o\"$$$$ooooo$$$$$$$$$$$$$$$$$$$$$$\"o$$$$$$$",
"$$$$$$$$$$o\"\"$$$$$$$$$$$$$$$$$$$$$$$$$\"oo$$$$$$$$$",
"$$$$$$$$$$$$$o$\"\"$$$$$$$$$$$$$$$$$\"\"oo$$$$$$$$$$$$",
"$$$$$$$$$$$$$$$$$$o$o$\"$\"$\"$\"$oo$o$$$$$$$$$$$$$$$$",
  NULL
};

/* KBDUS means US Keyboard Layout. This is a scancode table
*  used to layout a standard US keyboard. I have left some
*  comments in to give you an idea of what key is what, even
*  though I set it's array index to 0. You can change that to
*  whatever you want using a macro, if you wish! */
unsigned char kbdus[128] =
{
    0,  27, '1', '2', '3', '4', '5', '6', '7', '8',	/* 9 */
  '9', '0', '-', '=', '\b',	/* Backspace */
  '\t',			/* Tab */
  'q', 'w', 'e', 'r',	/* 19 */
  't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',	/* Enter key */
    0,			/* 29   - Control */
  'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',	/* 39 */
 '\'', '`',   0,		/* Left shift */
 '\\', 'z', 'x', 'c', 'v', 'b', 'n',			/* 49 */
  'm', ',', '.', '/',   0,				/* Right shift */
  '*',
    0,	/* Alt */
  ' ',	/* Space bar */
    0,	/* Caps lock */
    0,	/* 59 - F1 key ... > */
    0,   0,   0,   0,   0,   0,   0,   0,
    0,	/* < ... F10 */
    0,	/* 69 - Num lock*/
    0,	/* Scroll Lock */
    0,	/* Home key */
    0,	/* Up Arrow */
    0,	/* Page Up */
  '-',
    0,	/* Left Arrow */
    0,
    0,	/* Right Arrow */
  '+',
    0,	/* 79 - End key*/
    0,	/* Down Arrow */
    0,	/* Page Down */
    0,	/* Insert Key */
    0,	/* Delete Key */
    0,   0,   0,
    0,	/* F11 Key */
    0,	/* F12 Key */
    0,	/* All other keys are undefined */
};		

unsigned char key_status[128];

unsigned char kbdus_shifted[128] = 
{
    0,  27, '!', '@', '#', '$', '%', '&', '/', '*',	/* 9 */
  '(', ')', '_', '+', '\b',	/* Backspace */
  '\t',			/* Tab */
  'Q', 'W', 'E', 'R',	/* 19 */
  'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',	/* Enter key */
    0,			/* 29   - Control */
  'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',	/* 39 */
 '\"', '`',   0,		/* Left shift */
 '|', 'Z', 'X', 'C', 'V', 'B', 'N',			/* 49 */
  'M', '<', '>', '?',   0,				/* Right shift */
  '*',
    0,	/* Alt */
  ' ',	/* Space bar */
    0,	/* Caps lock */
    0,	/* 59 - F1 key ... > */
    0,   0,   0,   0,   0,   0,   0,   0,
    0,	/* < ... F10 */
    0,	/* 69 - Num lock*/
    0,	/* Scroll Lock */
    0,	/* Home key */
    0,	/* Up Arrow */
    0,	/* Page Up */
  '-',
    0,	/* Left Arrow */
    0,
    0,	/* Right Arrow */
  '+',
    0,	/* 79 - End key*/
    0,	/* Down Arrow */
    0,	/* Page Down */
    0,	/* Insert Key */
    0,	/* Delete Key */
    0,   0,   0,
    0,	/* F11 Key */
    0,	/* F12 Key */
    0,	/* All other keys are undefined */
};	

struct str_con_chardev {
	chardev cd;
	uint_16 cursor_col;
	uint_16 cursor_row;
	uint_16 color;
	uint_8 *screen;
	struct str_con_chardev *next;
	struct str_con_chardev *prev;
	sem_t sem;	
	char key_buffer;
} __attribute__((packed));

typedef struct str_con_chardev con_chardev;

con_chardev *con_current;
con_chardev con_consoles[MAX_CONSOLES];
 
#define ADJ_CURSOR \
		if (con_dev->cursor_row >= VGA_SCREEN_ROWS) { \
			con_dev->cursor_col = 0; \
			con_scroll(this, 1); \
		} \
		if (con_dev->cursor_col >= VGA_SCREEN_COLS) { \
			con_dev->cursor_row++; \
			con_dev->cursor_col = con_dev->cursor_col % VGA_SCREEN_COLS; \
		}

#define PREV_CURSOR \
		if (con_dev->cursor_col > 0) { \
			con_dev->cursor_col--; \
		} \
		else { \
			if (con_dev->cursor_row > 0) { \
				con_dev->cursor_row--; \
				con_dev->cursor_col = VGA_SCREEN_COLS - 1; \
			} \
		} \

#define PUT_CURSOR(ch) \
		*(con_dev->screen + (con_dev->cursor_row * VGA_SCREEN_COLS + con_dev->cursor_col) * 2) = ch; \
		*(con_dev->screen + (con_dev->cursor_row * VGA_SCREEN_COLS + con_dev->cursor_col) * 2 + 1) = con_dev->color; \
		con_dev->cursor_col++;
		
#define LF_CURSOR \
		con_dev->cursor_col = 0; \
		con_dev->cursor_row++;
		
#define TAB_CURSOR \
		con_dev->cursor_col+=4;

//esta funcion hace scroll de 1 linea en la consola
void con_scroll(chardev *this, uint_8 rows)
{
	con_chardev *con_dev;

	con_dev = (con_chardev *) this;

	uint_32 dif = (rows * VGA_SCREEN_COLS) << 1;
	uint_32 all = (VGA_SCREEN_ROWS * VGA_SCREEN_COLS) << 1;
	
	memcpy( con_dev->screen, con_dev->screen + dif, all - dif);
	memset( con_dev->screen + all - dif, '\0', dif);
	
	if ( con_dev->cursor_row >= rows )
		con_dev->cursor_row -= rows;
	else
	{
		con_dev->cursor_row = 0;
		con_dev->cursor_col = 0;
	}
}

//esta funcion imprime en la pantalla de la consola (simil vga_write)
sint_32 con_print(chardev *this, const char * msg, uint_32 size) {

	int i;
	con_chardev *con_dev;

	con_dev = (con_chardev *) this;

	for(i = 0; i < size; i++) {
		switch (msg[i]) {
			case '\n':
				ADJ_CURSOR
				LF_CURSOR
					break;
			case '\t':
				ADJ_CURSOR
				TAB_CURSOR
					break;
			case '\b':
				PREV_CURSOR
				PUT_CURSOR(' ')
				con_dev->cursor_col--;
				break;
			default:
				ADJ_CURSOR
				PUT_CURSOR(msg[i])
		}
	}
	return i;
}

int con_load_screen(chardev *this) {

	if (NULL == this) {
		return 1;
	}

	con_chardev *con_dev;

	con_dev = (con_chardev *) this;

	vga_load_screen((const char *) con_dev->screen);

	return 0;
}

int con_next() {
	if(NULL == con_current) {
		return 1;
	}
	 
	con_current = con_current->next;
	vga_load_screen((char *) con_current->screen);

	return 0;
}

int con_prev() {

	if(NULL == con_current) {
		return 1;
	}

	con_current = con_current->prev;
	vga_load_screen((char *) con_current->screen);

	return 0;
}

int con_key_press(unsigned char scancode) {
	//key es el scancode
	unsigned char ascii = 0;

	//seteo el status de las key
 	if (scancode & 0x80) {
		//released
		key_status[scancode & 127] = 0;
	} 
	else {
		//presed
		key_status[scancode & 127] = 1;
	}

	if (NULL == con_current) {
		return 0;
	}

	//solo tengo en cuenta las teclas presionadas para los caracterec ascii
	if (!(scancode & 0x80)) {
		//pressed
		
		//chequeo si hay q cambiar a la sig consola
		if (CON_SCANCODE_RIGHT == (scancode & 127) && key_status[CON_SCANCODE_ALT]) {
			con_next();
			return 1;
		}
		
		//chequeo si hay q cambiar a la prev consola
		if (CON_SCANCODE_LEFT == (scancode & 127) && key_status[CON_SCANCODE_ALT]) {
			con_prev();
			return 1;
		}
		
		if (key_status[CON_SCANCODE_L_SHIFT] || key_status[CON_SCANCODE_R_SHIFT]) {
			ascii = kbdus_shifted[scancode & 127];
		}
		else {
			ascii = kbdus[scancode & 127];
		}
		if (ascii) {
			con_current->key_buffer = ascii; 
			sem_signaln(&(con_current->sem));
		}

		return 1;
	}

	return 0;
}

sint_32 con_read(chardev* this, void* buf, uint_32 size) {

	if (NULL == this || NULL == buf) {
		return 0;
	}

	con_chardev *con_dev;
	char *usr_buf;
	uint_32 c;

	usr_buf = (char *) buf;
	con_dev = (con_chardev *) this;
	c = size;

	while (c) {
		sem_wait(&(con_dev->sem));
		*usr_buf = con_dev->key_buffer;
		c--;
		usr_buf++;
	}

	return size;
}



sint_32 con_write(chardev* this, const void* buf, uint_32 size) {

	if (NULL == this || NULL == buf) {
		return 0;
	}

	con_chardev *con_dev;

	con_dev = (con_chardev *) this;

	//si tengo el token imprimo en la pantalla fisica
	if (((uint_32) con_current) == ((uint_32) this)) {
		vga_print(con_dev->cursor_row, con_dev->cursor_col, buf, con_dev->color, size);
	}
	//imprimo en la pantalla virtual de la consola
	return con_print(this, buf, size);
}

void con_draw_ascii_art() {
	int i;

	vga_clear();
	vga_write(1,0, "  !!BAD!!OS", CON_DEFAULT_COLOR);	
	vga_write(2,0, "  ver. 0.01", CON_DEFAULT_COLOR);	
	vga_write(22,67, "Powered by:", CON_DEFAULT_COLOR);	
	vga_write(23,67, "  INTEL", CON_DEFAULT_COLOR);	

	vga_write(20,0, "  Created by:", CON_DEFAULT_COLOR);	
	vga_write(21,0, "    Benito", CON_DEFAULT_COLOR);	
	vga_write(22,0, "    Adrian", CON_DEFAULT_COLOR);	
	vga_write(23,0, "    Dardo", CON_DEFAULT_COLOR);	

	for (i = 0; ascii_art[i] != NULL; i++) {
		vga_write(i + 0, 15, ascii_art[i], CON_DEFAULT_COLOR);
	}
}

uint_32 con_flush(chardev* this) {

	if (NULL == this) {
		return 1;
	}

	con_chardev *con_dev;

	con_dev = (con_chardev *) this;

	if (con_dev->cd.clase == FREEDEV) {
		return 1;
	}

	//libero la pagina usada para la pantalla virtual
	mm_mem_free(con_dev->screen);
	//marco el registro como libre
	con_dev->cd.clase = FREEDEV;

	if (con_dev->next == con_dev) {
		//es el unico en la ronda 
		con_current = NULL;
		//muestro el ascii art
		con_draw_ascii_art();
	}
	else {
		//no es el unico de la ronda
		con_dev->prev->next = con_dev->next;
		con_dev->next->prev = con_dev->prev;
		if (con_current == con_dev) { 
			//si era el actual
			con_next();
		}
	}

	return 0;
}

chardev* con_open(void) {
 
	con_chardev *con_dev; 	
	int i;

	//busco una consola libre
	for (i = 0; i < MAX_CONSOLES; i++) {
 		if (FREEDEV == con_consoles[i].cd.clase) {
			con_consoles[i].cd.clase = CHARDEV;
			break;
		}
	}

	if (MAX_CONSOLES == i) {
		return NULL;
	}

	con_dev = &con_consoles[i];

	con_dev->screen = mm_mem_kalloc();
	if (NULL == con_dev->screen) {
		con_dev->cd.clase = FREEDEV;
		return NULL;
	}
	memset(con_dev->screen, 0, PAGE_SIZE);

	con_dev->cd.clase = CHARDEV;
	con_dev->cd.refcount = 1;
	con_dev->cd.flush = con_flush;
	con_dev->cd.read = con_read;
	con_dev->cd.write = con_write;
	con_dev->cd.seek = NULL;
	con_dev->sem = SEM_NEW(0);
	con_dev->cursor_col = 0;
	con_dev->cursor_row = 0;
	con_dev->color = CON_DEFAULT_COLOR;

	//La meto en el circulo de consolas adelante de la actual
	if (NULL == con_current) {
		con_dev->next = con_dev;
		con_dev->prev = con_dev;
		con_current = con_dev;
	}
	else {
		con_dev->next = con_current->next;
		con_dev->prev = con_current;
		con_current->next = con_dev;
		con_dev->next->prev = con_dev;
	}

	//Salto a la consola nueva
	con_next();	

	return (chardev *) con_dev;
}



void con_init() {
	memset(con_consoles, 0, MAX_CONSOLES * sizeof(con_chardev));
	con_current = NULL;
	//Dibujar ascii art
	con_draw_ascii_art();
	memset(key_status, 0, 128);
}
